# -*- coding: utf-8 -*-
import log, pttimers
import hall_glbs, hall_object, hall_errcode, hall_callback
import struct, random
import td_const, td_error, td_cmd
import td_room_player_s

#处理房间逻辑的类，要求继承hall_object.HallRoom，从进入游戏房间到开始游戏前，准备界面的逻辑基本上在这个类里面处理
class CRoomLogic(hall_object.HallRoom):
	def __init__(self, room_id = 0, name = "", mode = 0, host = 0, pwd = "", max_num=1, can_match=False):
		super(CRoomLogic, self).__init__(room_id, name, mode, host, pwd, max_num, can_match)
		self.dicSidePlayers = {}			#自定义玩家标志：房间内玩家对象的字典
		self.dicUidPlayers = {}				#玩家uid：房间内玩家对象的字典
		self.dicHidPlayers = {}				#玩家hid：房间内玩家对象的字典
		
		
		self.iStatus = td_const.GAME_WAIT		#房间状态（准备/游戏初始化/游戏中）
		#注册定时器
		
		#self.objTimer = pttimers.CTimer(seconds=float(1)/td_const.FPS_SERVER, callback=self.on_timer, forever=True)
		#self.objTimer.start()
		
		self.iStage = None				#当前选中的关卡
		self.iMaster = None				#当前的房主的玩家标识
		self.objMsgMgr = hall_callback.get_game_room_msgmgr()		#传输对象的引用，消息打包的时候用到
		#self.objGameLogic = td_mapmgr_s.CMapMgr(self)			#处理游戏逻辑的类
		#日志记录房间创建相关信息
		self.cglog(td_const.LOGTYPE_KEYPOINT, "[NEW_ROOM][id=%s;name=%r;mode=%s;max_player=%s;can_match=%s]" % (room_id, name, mode, max_num, can_match))
	
	#获取空余可用的玩家标识
	def get_empty_side(self):
		iSide = None
		for i in xrange(self.max_num):
			if not self.dicSidePlayers.has_key(i):
				iSide = i
				break
		return iSide
	#基类的虚函数，当玩家从大厅登陆进某个房间的时候，会调用这个函数，_player是大厅中的玩家对象
	def cghall_on_player_enter_room(self, _player, obj):
		#只有房间处于准备状态下，才接受新玩家的进入
		if self.iStatus != td_const.GAME_WAIT:
			return td_error.ERROR_ENTER_ROOM_STARTED
		#获取空余的玩家标识，如果获取不到，则拒绝此玩家登陆房间
		iSide = self.get_empty_side()
		if iSide is None:
			return td_error.ENTER_ROOM_NO_SEAT

		#生成房间内玩家对象
		objPlayer = td_room_player_s.CPlayer(self, _player, iSide)
		#先把玩家加入玩家列表（因为底层接口不允许对玩家列表外的玩家发送消息）
		self.add_player(objPlayer)
		#日志记录玩家登陆
		self.cglog(td_const.LOGTYPE_KEYPOINT, "[PLAYER_ENTER][uid=%s;side=%s]" % (objPlayer.uid, objPlayer.iSide))
		#第一个进入的玩家自动成为房主，并且当前关卡切换为房主的选中关卡
		if self.iMaster is None:
			self.iMaster = objPlayer.iSide
			self.iStage = objPlayer.iSelectStage
		#向新玩家发送游戏模式信息
		#self.send_game_mode(objPlayer)
		#向新玩家发送自己进入的消息
		self.send_user_in(objPlayer, objPlayer)
		self.send_player_secret_info(objPlayer)

		#向旧玩家发送新玩家进入的消息（同时向新玩家发送旧玩家进入的消息）
		for other_player in self.dicSidePlayers.itervalues():
			if other_player.uid == objPlayer.uid:
				continue
			self.send_user_in(other_player, objPlayer)
			self.send_user_in(objPlayer, other_player)
		#检测是否可以开始倒数
		#self.check_countdown()
		return td_error.SUCCESS
	#基类的虚函数，当玩家从某个房间退出的时候，会调用这个函数，hid是玩家的socket
	def cghall_on_player_leave_room(self, hid):
		#获取要退出的玩家的对象
		objPlayer = self.cghall_get_player_by_hid(hid)
		if not objPlayer:
			return td_error.LEAVE_ROOM_NO_PLAYER

		#如果房间状态不处于准备，那么此玩家记录断线（逃跑）一次
		if self.iStatus != td_const.GAME_WAIT:
			self.cghall_inc_draw_count(objPlayer.hid)
			#日志记录玩家逃跑
			self.cglog(td_const.LOGTYPE_KEYDATA, "[PLAYER_ESCAPE][uid=%s]" % objPlayer.uid)
		#从玩家列表里删除这个玩家
		self.del_player(objPlayer)
		#日志记录玩家退出
		self.cglog(td_const.LOGTYPE_KEYPOINT, "[PLAYER_LEAVE][uid=%s;side=%s]" % (objPlayer.uid, objPlayer.iSide))

		#如果正在游戏中，告诉游戏逻辑当前有效玩家少了一个，假如此时满足了游戏结算的条件，就会调用gameend的逻辑
		if self.iStatus == td_const.GAME_PLAYING:
			#self.objGameLogic.player_leave(objPlayer.iSide)
			pass
		#向客户端广播有玩家退出的消息
		self.send_user_out(objPlayer)
		
		#如果处于游戏初始化阶段，有玩家退出则当前游戏直接结束，此局不算成绩
		if self.iStatus == td_const.GAME_INIT:
			#self.on_game_end(td_const.FACTION_NONE, [])
			pass
		
		#如果退出的正好是房主，那么选择一个新房主
		if self.iMaster == objPlayer.iSide:
			master = self.get_random_player()
			if master:
				self.iMaster = master.iSide
				self.iStage = master.iSelectStage
			else:
				self.iMaster = None
				self.iStage = None
				
		#检测是否满足立刻进入游戏初始化阶段的条件（比如说唯一没有准备的玩家退出了），满足则进入游戏初始化阶段
		if self.check_game_init():
			#self.on_game_init()
			pass
		#否则检测是否满足倒数条件
		else:
			#self.check_countdown()
			pass
		return td_error.SUCCESS
		
	#重载基类的函数，在房间对象被销毁前，大厅逻辑会调用这个函数，需要在这个函数里清除自己创建的各种引用，释放内存，避免出现内存泄漏
	def cghall_clean(self):
		super(CRoomLogic, self).cghall_clean()
		#调用游戏逻辑的清理函数并清除对游戏逻辑对象的引用
		#self.objGameLogic.clean()
		#self.objGameLogic = None
		#清除对传输对象的引用
		self.objMsgMgr = None
		#遍历玩家列表，调用每个玩家对象的清理函数
		for objPlayer in self.dicSidePlayers.itervalues():
			objPlayer.clean()
		#清空管理玩家对象的三个字典
		self.dicSidePlayers.clear()
		self.dicUidPlayers.clear()
		self.dicHidPlayers.clear()
		#中止并清理timer
		#self.objTimer.stop()
		#self.objTimer.delete()
		#self.objTimer = None
		#日志记录房间自清理完毕，等待释放
		self.cglog(td_const.LOGTYPE_KEYPOINT, "[DEL_ROOM]")
		
		
		
	def on_player_leave_game(self, objPlayer, obj):
		self.cghall_tell_hall_player_leave_room(objPlayer.hid)
		
		self.log.info("on player leave room hid:%s"%objPlayer.hid)
	
	
	def on_player_change_score(self, objPlayer, msg):
		##获取player标识
		player = self.cghall_get_player_by_hid(objPlayer.hid);
		
		self.do_change_score(player, msg.delta_score)
		
		#### 生成消息告诉其它客户端player_sc_update_score
		objmsg = self.objMsgMgr.player_sc_update_score()
		objmsg.uid = player.hid
		objmsg.current_score = player.score
		self.send_all(objmsg,[player.hid,])
	
	
	
	def do_change_score(self, player, delta):
		
		#self.cghall_inc_score(player.hid, delta)
		player.score += delta

		## ## 生成消息告诉客户端
		retmsg = self.objMsgMgr.game_sc_change_score()
		retmsg.delta_score = delta
		retmsg.current_score = player.score
		self.send_to_user(player, retmsg)
		
		self.cglog(td_const.LOGTYPE_KEYPOINT,\
		'change score. uid:%s delta:%s newscore:%s'\
				%( player.uid, delta,player.score) )				
        

	
	#
	#检测是否满足立刻进入游戏初始化状态的函数（所有玩家都ready）
	def check_game_init(self):
		total = len(self.dicSidePlayers)
		if total <= 0:
			return False
		ready = 0
		for objPlayer in self.dicSidePlayers.itervalues():
			if objPlayer.bIsReady:
				ready += 1
		if total == ready:
			return True
		else:
			return False
	
	#房间进入游戏开始状态时调用的函数
	def on_game_start(self):
		#再次调用基类的函数，通知大厅游戏已经开始了（某种情况下（传送带模式）会跳过游戏初始化状态，直接从准备状态进入游戏中状态）
		self.cghall_tell_hall_game_start()
		pass
	#重载了基类的同名函数，让响应客户端发送过来的消息的回调函数中的objplayer参数是游戏自定义的玩家对象，而不是大厅的玩家对象
	def cghall_get_player_by_uid(self, uid):
		return self.dicUidPlayers.get(uid, None)
	
	#重载了基类的同名函数，让响应客户端发送过来的消息的回调函数中的objplayer参数是游戏自定义的玩家对象，而不是大厅的玩家对象
	def cghall_get_player_by_hid(self, hid):
		return self.dicHidPlayers.get(hid, None)
		
	#此函数把一个玩家对象添加到管理玩家对象的三个字典中
	def add_player(self, objPlayer):
		self.dicSidePlayers[objPlayer.iSide] = objPlayer
		self.dicUidPlayers[objPlayer.uid] = objPlayer
		self.dicHidPlayers[objPlayer.hid] = objPlayer
	
	#此函数调用玩家对象的自清理函数并把这个玩家对象从管理玩家对象的三个字典中删除
	def del_player(self, objPlayer):
		objPlayer.clean()
		if self.dicSidePlayers.has_key(objPlayer.iSide):
			del self.dicSidePlayers[objPlayer.iSide]
		if self.dicUidPlayers.has_key(objPlayer.uid):
			del self.dicUidPlayers[objPlayer.uid]
		if self.dicHidPlayers.has_key(objPlayer.hid):
			del self.dicHidPlayers[objPlayer.hid]
	
	#此函数随机获取一个玩家对象，目前只有切换房主的时候在用
	def get_random_player(self):
		lst = self.dicSidePlayers.values()
		if lst:
			return random.choice(lst)
		else:
			return None
	#--------------------------------------
	##以下是打包并发送消息的函数
	#发送消息给所有人
	def send_all(self, obj, exclude = []):
		self.cghall_broadcast(obj, exclude)
			
	#发送消息给某人
	def send_to_user(self, objPlayer, obj):
		self.cghall_send(objPlayer.hid, obj)
		
	def send_user_in(self, target, objPlayer):
		pass
	
	#发送玩家退出消息
	def send_user_out(self, objPlayer):
		pass
	def send_player_secret_info(self, objPlayer):
		pass
		
	##以下是日志相关
	def cglog(self, iType, text):
		text = td_const.LOG_KEYWORD_DIC[iType] + text
		if iType == td_const.LOGTYPE_DEEP:
			self.log.deep(text)
		elif iType == td_const.LOGTYPE_DEBUG:
			self.log.debug(text)
		elif iType == td_const.LOGTYPE_KEYPOINT:
			self.log.info(text)
		elif iType == td_const.LOGTYPE_KEYDATA:
			self.log.info(text)
		elif iType == td_const.LOGTYPE_DESIGN:
			self.log.warn(text)
		elif iType == td_const.LOGTYPE_CONFIG:
			self.log.warn(text)
		elif iType == td_const.LOGTYPE_ERROR:
			self.log.error(text)